iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0

GStreamer設計了一些手段讓應用程式可以跟Pipeline的資料互動,其中一種就是探針(Probe),呼叫者可以將GstProbe戳在元件的Pad上,如此一來可以直接操作流經Pad的數據。今天就跟大家介紹怎麼使用GstProbe去看到DeepStream附加的MetaData

範例程式

為了節省篇幅,請大家直接複製Day 17的main.c和Makefile進行修改

修改main.c,[...]代表省略,方便判斷新增的程式碼要加在哪裡

#include <gst/gst.h>
#include <gstnvdsmeta.h>

#define DEMO_VIDEO_FILE "/opt/nvidia/deepstream/deepstream/samples/streams/sample_1080p_h264.mp4"
#define OUTPUT_FILE "/ironman/day_18_demo.mp4"
#define PGIE_CONFIG_FILE "/opt/nvidia/deepstream/deepstream/sources/apps/sample_apps/deepstream-test1/dstest1_pgie_config.txt"

[...]

static gboolean pad_is_nvmm_video(GstPad* pad) {
    [...]
}

static GstPadProbeReturn handle_osd_sink_pad_probe(GstPad*, GstPadProbeInfo* info, gpointer) {
    /* 'buf' is owned by 'info', we don't need to release it */
    GstBuffer* buf = gst_pad_probe_info_get_buffer(info);

    /* gst_pad_probe_info_get_buffer may return NULL */
    if (!buf) {
        g_print("WARNING: Can't get GstBuffer from GstPadProbeInfo\n");
        return GST_PAD_PROBE_OK;
    }

    NvDsBatchMeta* batch_meta = gst_buffer_get_nvds_batch_meta(buf);
    if (!batch_meta) {
        g_print("WARNING: No NvDsBatchMeta was attched on the GstBuffer\n");
        return GST_PAD_PROBE_OK;
    }

    for (NvDsFrameMetaList* l_frame = batch_meta->frame_meta_list; l_frame; l_frame = l_frame->next) {
        NvDsFrameMeta* frame_meta = l_frame->data;
        g_print("frame_num: %" G_GINT32_FORMAT ", num_objects: %" G_GUINT32_FORMAT "\n",
            frame_meta->frame_num, frame_meta->num_obj_meta);

        /* Prevent too many messages be printed. */
        gboolean should_print_object = (frame_meta->frame_num % 90) == 0;

        for (NvDsObjectMetaList* l_obj = frame_meta->obj_meta_list; l_obj; l_obj = l_obj->next) {
            NvDsObjectMeta* obj_meta = l_obj->data;

            if (should_print_object) {
                /* This is the final bbox plotted on the screen */
                const NvOSD_RectParams* rect = &obj_meta->rect_params;
                g_print("object: {label=%s,x=%" G_GUINT32_FORMAT ",y=%" G_GUINT32_FORMAT
                    ",w=%" G_GUINT32_FORMAT ",h=%" G_GUINT32_FORMAT "}\n",
                    obj_meta->text_params.display_text,
                    (guint)rect->left, (guint)rect->top,
                    (guint)rect->width, (guint)rect->height);

                /* Prevent too many messages be printed. */
                should_print_object = FALSE;
            }

            /* Remain for user to access the object meta */
        }
    }

    return GST_PAD_PROBE_OK;
}

[...]

static GstElement* create_demo_pipeline(const gchar* demo_video_file, const gchar* output_file) {
    [...]

    /* Set signal callback for srcbin */
    (void)g_signal_connect(srcbin, "pad-added", G_CALLBACK(on_srcbin_pad_added), streammux);

    GstPad* osd_sink_pad = gst_element_get_static_pad(osd, "sink");

    /* Use GST_PAD_PROBE_TYPE_BUFFER for looking GstBuffer in the probe callback */
    (void)gst_pad_add_probe(osd_sink_pad, GST_PAD_PROBE_TYPE_BUFFER, handle_osd_sink_pad_probe, NULL, NULL);

    return pipeline;
}

修改Makefile,字數不多就直接改整份,重點是新增連接DeepStream的程式庫

CC := gcc
TARGET := demo_app
SRCS := main.c
OBJS := $(SRCS:.c=.o)

DS_DIR := /opt/nvidia/deepstream/deepstream
PKGS := gstreamer-1.0

CFLAGS += -Wall -Wextra $(shell pkg-config --cflags $(PKGS))
LIBS += $(shell pkg-config --libs $(PKGS))

CFLAGS += -I$(DS_DIR)/sources/includes
LIBS += -L$(DS_DIR)/lib -lnvdsgst_meta \
	-Wl,-rpath,$(DS_DIR)/lib

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(OBJS) -o $@ $(LIBS)

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f $(OBJS) $(TARGET)

.PHONY: all clean

編譯及執行

make && ./demo_app

會看到像這樣的輸出

[...]
frame_num: 1350, num_objects: 23
object: {label=person,x=397,y=481,w=31,h=80}
frame_num: 1351, num_objects: 23
frame_num: 1352, num_objects: 23
frame_num: 1353, num_objects: 23
frame_num: 1354, num_objects: 23
frame_num: 1355, num_objects: 23
[...]

範例解析

include and link

這次的範例需要從GstBuffer取得DeepStream附加的Metadata,也就是NvDsBatchMeta,DeepStream把所有Metadata都附加在這個結構底下。NvDsBatchMeta是定義在nvdsmeta.h,但是必須引用gstnvdsmeta.h才有gst_buffer_get_nvds_batch_meta的宣告,而gst_buffer_get_nvds_batch_meta的符號是在libnvdsgst_meta.so內。

基於上述的變動,我們要在Makefile加上DeepStream的include路徑讓compiler使用

CFLAGS += -I$(DS_DIR)/sources/includes

linker的部分則是加上以下這段,加上-rpath這個參數可以在runtime的時候去該路徑搜尋lib,不必在LD_LIBRARY_PATH加上路徑

LIBS += -L$(DS_DIR)/lib -lnvdsgst_meta \
    -Wl,-rpath,$(DS_DIR)/lib

新增Probe

GstPad* osd_sink_pad = gst_element_get_static_pad(osd, "sink");

/* Use GST_PAD_PROBE_TYPE_BUFFER for looking GstBuffer in the probe callback */
(void)gst_pad_add_probe(osd_sink_pad, GST_PAD_PROBE_TYPE_BUFFER, handle_osd_sink_pad_probe,NULL, NULL);

gst_pad_add_probe文件

gulong
gst_pad_add_probe (GstPad * pad,
                   GstPadProbeType mask,
                   GstPadProbeCallback callback,
                   gpointer user_data,
                   GDestroyNotify destroy_data)

GstProbe是掛在在GstPad上,這邊我們選用nvdsosd的sink,因為這個位置已經有nvinfer附加的Metadata。那麼Probe的附加方式有很多種,我們只要看GstBuffer的話就是選擇GST_PAD_PROBE_TYPE_BUFFER,這個GstProbe會「串聯」在Sink Pad前面。

Probe Callback和迭代Metadata

static GstPadProbeReturn handle_osd_sink_pad_probe(GstPad*, GstPadProbeInfo* info, gpointer) {
    [...]
}

我們得先用以下方式取得NvDsBatchMeta,記得這邊要檢查是否為NULL,因為如果你Probe位置放錯,或是前面元件接錯,都有可能導致GstBuffer上沒有附加NvDsBatchMeta

NvDsBatchMeta* batch_meta = gst_buffer_get_nvds_batch_meta(buf);
    if (!batch_meta) {
        g_print("WARNING: No NvDsBatchMeta was attched on the GstBuffer\n");
        return GST_PAD_PROBE_OK;
    }

接著是各種NvDs<Type>MetaList,全部都是GList的別名

typedef GList NvDsFrameMetaList;
typedef GList NvDsUserMetaList;
typedef GList NvDsObjectMetaList;
typedef GList NvDisplayMetaList;
typedef GList NvDsClassifierMetaList;
typedef GList NvDsLabelInfoList;

GList是Linked List資料結構,迭代的方式只要遵循以下寫法就可以,其他不同Metadata也是完全一樣的做法

for (NvDsFrameMetaList* l_frame = batch_meta->frame_meta_list; l_frame; l_frame = l_frame->next) {
    NvDsFrameMeta* frame_meta = l_frame->data;
    /* Some stuff with NvDsFrameMeta */
}

最後講一下為什麼Bounding Box是從obj_meta->rect_params取出

 typedef struct _NvDsObjectMeta {
   NvDsBaseMeta base_meta;
   struct _NvDsObjectMeta *parent;
   gint unique_component_id;
   gint class_id;
   guint64 object_id;
   NvDsComp_BboxInfo detector_bbox_info;
   NvDsComp_BboxInfo tracker_bbox_info;
   gfloat confidence;
   gfloat tracker_confidence;
   NvOSD_RectParams rect_params;
   NvOSD_MaskParams mask_params;
   NvOSD_TextParams text_params;
   gchar obj_label[MAX_LABEL_SIZE];
   NvDsClassifierMetaList *classifier_meta_list;
   NvDsUserMetaList *obj_user_meta_list;
   gint64 misc_obj_info[MAX_USER_FIELDS];
   gint64 reserved[MAX_RESERVED_FIELDS];
 }NvDsObjectMeta;

因為DeepStream除了PGIE會找出Bounding Box之外,還有Tracker可以預測Bounding Box。detector_bbox_info是儲存PGIE的輸出,tracker_bbox_info是儲存Tracker的輸出,rect_params是最終nvdsosd畫出來的Bounding Box。由於Tracker在預測完之後會把結果覆寫到rect_params,所以才要以它為基準

如果有興趣的人可以去查這份原始碼
/opt/nvidia/deepstream/deepstream/sources/gst-plugins/gst-nvinfer/gstnvin fer_meta_utils.cpp,裡面的attach_metadata_detector函式有一個片段有附上說明

/* Assign bounding box coordinates. These can be overwritten if tracker component is present in the pipeline */
rect_params.left = obj.left;
rect_params.top = obj.top;
rect_params.width = obj.width;
rect_params.height = obj.height;

結語

經過今天的努力,想必漸漸地可以感受到先前看到的那些範例畫面是怎麼出現的,接下來會循序漸進,繼續從程式碼的角度繼續解析DeepStream的內容。


上一篇
[Day 17] 認識GStreamer
下一篇
[Day 19] 細說各種Metadata
系列文
深入認識DeepStream,不只是停在執行範例 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言